#include "ai_player.hpp"
#include "asserts.hpp"
#include "battle.hpp"
#include "draw_scenario.hpp"
#include "foreach.hpp"
#include "frame_rate_handler.hpp"
#include "human_player.hpp"
#include "pathfind.hpp"
#include "scenario.hpp"
#include "tile_logic.hpp"
#include "unit_utils.hpp"
#include "wm.hpp"
#include "wml_node.hpp"
#include "wml_utils.hpp"

namespace game {

namespace {
int distance_to_nearest_enemy(const unit& unit, const hex::location& loc)
{
	int nearest_enemy = -1;
	const scenario::UnitMap& unit_map = scenario::current()->unit_map();
	for(scenario::UnitMap::const_iterator i = unit_map.begin(); i != unit_map.end(); ++i) {
		const int distance = hex::distance_between(loc, i->first);
		if((nearest_enemy == -1 || distance < nearest_enemy) && units_hostile(unit, *i->second)) {
			nearest_enemy = distance;
		}
	}

	return nearest_enemy;
}

}

int ai_player::value_spacing(const hex::location& unit_loc, const hex::location& commander_loc) const
{
	return abs(unit_spacing_ - hex::distance_between(unit_loc, commander_loc));
}

int ai_player::evaluate_move(const unit& unit, const hex::location& move_to) const
{
	return (value_spacing(unit.loc(), unit.get_commander().loc()) -
	        value_spacing(move_to, unit.get_commander().loc()))*100 +
		   (distance_to_nearest_enemy(unit, unit.loc()) -
		    distance_to_nearest_enemy(unit, move_to))*enemy_distance_weighting_;
}

ai_player::MoveOption ai_player::find_best_move(unit_ptr unit, const hex::route_map& routes) const
{
	MoveOption result;
	for(hex::route_map::const_iterator i = routes.begin(); i != routes.end(); ++i) {
		if(scenario::current()->get_unit(i->first)) {
			continue;
		}

		const int rating = evaluate_move(*unit, i->first);
		if(!result.move_to.valid() || rating > result.rating) {
			result.unit = unit;
			result.rating = rating;
			result.move_to = i->first;
		}
	}

	return result;
}

namespace {

int evaluate_attack(const unit& attacker, const unit& defender)
{
	const int attack_advantage = calculate_unit_attack(attacker, defender) - calculate_unit_defense(defender, attacker);
	const int defense_advantage = calculate_unit_defense(attacker, defender) - calculate_unit_attack(defender, attacker);

	const int hp_advantage = attacker.hitpoints() - defender.hitpoints();

	return attack_advantage - defense_advantage - hp_advantage;
}

struct AttackOption {
	unit_ptr attacker;
	unit_ptr target;
	hex::location attack_from;
	int rating;
};

AttackOption find_best_attack(unit_ptr unit, const hex::route_map& routes)
{
	std::vector<hex::location> nearby;
	AttackOption result;
	result.attacker = unit;
	for(hex::route_map::const_iterator i = routes.begin(); i != routes.end(); ++i) {
		nearby.clear();
		for(int n = 1; n <= unit->attack_range(); ++n) {
			get_tile_ring(i->first, n, nearby);
		}

		foreach(const hex::location& a, nearby) {
			unit_ptr target = scenario::current()->get_unit(a);
			if(!target || !units_hostile(*unit, *target)) {
				continue;
			}

			TempLocationSetter setter(*unit, a);
			const int rating = evaluate_attack(*unit, *target);
			if(!result.target || rating > result.rating) {
				result.target = target;
				result.attack_from = i->first;
				result.rating = rating;
			}
		}
	}
	return result;
}

}

ai_player::ai_player(wml::const_node_ptr node)
  : player(node), target_(node->attr("target")), passive_(wml::get_bool(node, "passive", false)), min_rating_for_leader_attack_(wml::get_int(node, "leader_attack_rating")), unit_spacing_(wml::get_int(node, "unit_spacing", 1)), enemy_distance_weighting_(wml::get_int(node, "enemy_distance_weighting", 1))
{
	wml::const_node_ptr target_hex = node->get_child("target_hex");
	if(target_hex) {
		target_hex_ = hex::location(wml::get_int(target_hex, "x", -1), wml::get_int(target_hex, "y", -1));
	}
}

void ai_player::play_turn(scenario& s)
{
	std::vector<commander_unit_ptr> cmd = commanders();
	foreach(commander_unit_ptr commander, cmd) {
		control_commander(s, commander);
	}
}

void ai_player::control_commander(scenario& s, commander_unit_ptr commander)
{
	game::set_scroll_target(commander->loc());

	//reduce the commander's movement to that of its slowest unit so it doesn't outrun
	//its units.
	foreach(const_unit_ptr soldier, commander->soldiers()) {
		if(soldier->moves_left() < commander->moves_left()) {
			commander->use_moves(commander->moves_left() - soldier->moves_left());
		}
	}

	hex::location target_loc = target_hex_;

	if(target_.empty() == false) {
		commander_unit_ptr target = s.get_commander_with_id(target_);
		if(target) {
			target_loc = target->loc();
		}
	}

	hex::route_map routes;
	hex::find_possible_moves(commander->loc(), commander->moves_left(), *commander, routes);

	AttackOption attack = find_best_attack(commander, routes);
	if(!passive_ && attack.target && attack.rating >= min_rating_for_leader_attack_) {
		move_unit(s, commander, routes[attack.attack_from]);
		commander->use_command();
		attack_unit(attack.attack_from, attack.target->loc());
	} else if(target_loc.valid()) {
		int best_distance = -1;
		hex::route_map::const_iterator best = routes.end();
		for(hex::route_map::const_iterator i = routes.begin(); i != routes.end(); ++i) {
			const int distance = hex::distance_between(i->first, target_loc);
			if(best == routes.end() || distance < best_distance) {
				best_distance = distance;
				best = i;
			}
		}

		if(best != routes.end() && !best->second.steps.empty()) {
			move_unit(s, commander, best->second);
			commander->use_command();
		}
	}

	//keep attacking while there are attack options available.
	while(!passive_ && !commander->destroyed() && commander->commands_left() > 0) {
		AttackOption best_attack;

		const std::vector<soldier_unit_ptr> soldiers = commander->soldiers();
		foreach(soldier_unit_ptr unit, soldiers) {
			routes.clear();
			hex::find_possible_moves(unit->loc(), unit->moves_left(), *unit, routes);
			hex::route_map::iterator ri = routes.begin();
			while(ri != routes.end()) {
				if(hex::distance_between(ri->first, commander->loc()) > commander->command_radius()) {
					routes.erase(ri++);
				} else {
					++ri;
				}
			}

			AttackOption attack = find_best_attack(unit, routes);
			if(attack.target && (!best_attack.target || attack.rating > best_attack.rating)) {
				best_attack = attack;
			}
		}

		if(best_attack.target) {
			routes.clear();
			hex::find_possible_moves(best_attack.attacker->loc(), best_attack.attacker->moves_left(), *best_attack.attacker, routes);
			move_unit(s, best_attack.attacker, routes[best_attack.attack_from]);
			attack_unit(best_attack.attack_from, best_attack.target->loc());
			commander->use_command();
		} else {
			//no more attacks left.
			break;
		}
	}

	std::cerr << "moving units..." << commander->commands_left() << "\n";

	//find moves for the unit to make
	while(!commander->destroyed() && commander->commands_left() > 0) {
		std::cerr << "finding move with " << commander->commands_left() << " cmds left\n";
		MoveOption best_move;

		const std::vector<soldier_unit_ptr> soldiers = commander->soldiers();
		foreach(soldier_unit_ptr unit, soldiers) {
			routes.clear();
			hex::find_possible_moves(unit->loc(), unit->moves_left(), *unit, routes);
			MoveOption move = find_best_move(unit, routes);
			if(!best_move.unit || move.rating > best_move.rating) {
				best_move = move;
			}
		}

		if(best_move.unit && best_move.rating > 0) {
			commander->use_command();
			routes.clear();
			hex::find_possible_moves(best_move.unit->loc(), best_move.unit->moves_left(), *best_move.unit, routes);
			std::cerr << "move unit: " << best_move.unit->loc().x() << "," << best_move.unit->loc().y() << " -> " << best_move.move_to.x() << "," << best_move.move_to.y() << "\n";
			move_unit(s, best_move.unit, routes[best_move.move_to]);
		} else {
			break;
		}
	}
	std::cerr << "done moving units..." << commander->commands_left() << "\n";

	display_moves(s);
}

void ai_player::move_unit(scenario& scenario, unit_ptr unit, const hex::route& route)
{
	if(route.steps.size() <= 1) {
		return;
	}

	unit->use_moves(route.cost);
	unit->move_along_path(route.steps);
	scenario.move_unit(unit, route.steps.back());
}

void ai_player::attack_unit(const hex::location& attacker_loc, const hex::location& defender_loc)
{
	unit_ptr attacker = scenario::current()->get_unit(attacker_loc);
	unit_ptr defender = scenario::current()->get_unit(defender_loc);
	ASSERT_LOG(attacker.get() != NULL && defender.get() != NULL, "Could not find unit involved in battle");

	frame_rate_handler fps;
	battle_ptr b(new battle(attacker, defender));
	while(!b->done()) {
		b->process();
		human_player::current_perspective()->draw(*scenario::current());
		b->draw();
		wm::swap_buffers();

		fps.next_frame();
		next_draw_cycle();
	}

	scenario::current()->remove_dead_units();
}

void ai_player::display_moves(const scenario& scenario)
{
	frame_rate_handler fps;
	while(units_moving()) {
		SDL_Event event;
		while(SDL_PollEvent(&event)) {
			switch(event.type) {
			case SDL_QUIT:
				throw human_player::game_aborted();
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym) {
				case SDLK_ESCAPE:
					throw human_player::game_aborted();
				}
			}
		}

		human_player::current_perspective()->draw(scenario);
		wm::swap_buffers();
		fps.next_frame();
		next_draw_cycle();
	}
}

void ai_player::set_value(const std::string& key, const variant& value)
{
	if(key == "target") {
		hex::location* loc = value.convert_to<hex::location>();
		if(loc) {
			target_hex_ = *loc;
		}
		return;
	}

	player::set_value(key, value);
}

}
